package org.equivalence.client;

import java.awt.AWTEventMulticaster;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;

import org.equivalence.common.FileRepository;

/**
 * this class updates a leopard client application based on information
 * sent back from a leopard update server
 * @author glenn
 *
 */
public class UpdateClient implements Runnable {
	private ActionListener transferEventListener;
	private ActionListener progressListener;
	private ActionListener completionListener;
	private ActionListener cancelledListener;

	FileReciever reciever;
	
	private String tempDirectory;
	private ArrayList<String> directories;
	HashMap<String,Boolean> excludeFiles;
	String location;
	int port;
	
	public UpdateClient(String location,int port) {
		directories = new ArrayList<String>();
		excludeFiles = new HashMap<String,Boolean> ();
		this.location = location;
		this.port = port;
		tempDirectory = "temp";
		
		//set up the file reciever for the update client
		reciever = new FileReciever(this);
		//allow the update client to cancel the transfer of files
		cancelledListener = AWTEventMulticaster.add(
				cancelledListener, reciever);
	}
	
	public void setServerLocation(String location) {
		this.location = location;
	}
	
	public void setServerPort(int port) {
		this.port = port;		
	}
	
	public String getServerLocation() {
		return location;
	}
	
	public int getServerPort() {
		return port;
	}
	
	public void addDirectory(String directory) {
		directories.add(directory);
	}
	
	public void clearDirectories() {
		directories.clear();
	}
	
	public String getDirectory(int i) {
		return directories.get(i);
	}
	
	public void removeDirectory(int i) {
		directories.remove(i);
	}
	
	public int getDirectoryCount() {
		return directories.size();
	}
	
	/**
	 * add a file that the versioning system should not bother versioning
	 * @param filename the file to exclude (relative to application path)
	 */
	public void addExcludeFile(String filename) {
		excludeFiles.put(filename, true);
	}
	
	public void removeExcludeFile(String filename) {
		excludeFiles.remove(filename);
	}
	
	/**
	 * @return the tempDirectory
	 */
	public final String getTempDirectory() {
		return tempDirectory;
	}

	/**
	 * @param tempDirectory the tempDirectory to set
	 */
	public final void setTempDirectory(String tempDirectory) {
		this.tempDirectory = tempDirectory;
	}

	/**
	 * subscribe to this listener if you wish to know about file transfer events
	 * such as the names of files being currently added or removed.
	 * @param al
	 */
	public void  addTransferEventListener(ActionListener al) {
		transferEventListener = AWTEventMulticaster.add(
				transferEventListener, al);
	}

	/**
	 * subscribe to this listener if you wish to know the progress of any transfers
	 * the source object will contain a float between 0 and 1, with 0 being incomplete and 1 being completed.
	 * @param al
	 */
	public void  addProgressListener(ActionListener al) {
		progressListener = AWTEventMulticaster.add(
				progressListener, al);
	}
	
	/**
	 * subscribe to this listener if you wish to know when a transfer completes
	 * the value returned in the source object is a description of the final state of the transfer
	 * and the actioncommand can be one of three values
	 * 	"complete" - completed successfully
	 *  "noupdate" - no updating done because none was nessecary
	 *  "error" - the transfer stopped due to error or user cancellation (observe description for more information regarding the exact reason)
	 * @param al
	 */
	public void  addCompletionListener(ActionListener al) {
		completionListener = AWTEventMulticaster.add(
				completionListener, al);
	}
	
	/**
	 * cancel the current transfer
	 *
	 */
	public synchronized void cancel() {
		if (cancelledListener != null) {
			cancelledListener.actionPerformed(new ActionEvent(
					0, ActionEvent.ACTION_PERFORMED,
					"cancelled"));
		}
	}
	
	/**
	 * message all listeners with a string in the source object corresponding
	 * to some interesting event that just occured during filetransfer such as a file
	 * being removed or added
	 * @param data the transfer event
	 */
	synchronized void transferEvent(String data) {
		if (transferEventListener != null) {
			transferEventListener.actionPerformed(new ActionEvent(
					data, ActionEvent.ACTION_PERFORMED,
					"message created"));
		}
	}
	
	/**
	 * set completion between 0 and 1
	 * @param completion completion between 0 and 1
	 */
	synchronized void setProgress(float progress) {
		if (progressListener != null) {
			progressListener.actionPerformed(new ActionEvent(
					progress, ActionEvent.ACTION_PERFORMED,
					"progress changed"));
		}
	}	

	/**
	 * sets the transfer as completed with the final status and description
	 * @param status final transfer status
	 * @param description description of final transfer status
	 */
	synchronized void completed(String status,String description) {
		if (completionListener != null) {
			completionListener.actionPerformed(new ActionEvent(
					description, ActionEvent.ACTION_PERFORMED,
					status));
		}
	}
	
	/**
	 * perform an update but in a thread so it doesn't block program
	 * execution
	 */
	public void run() {
		update();
	}
	
	/**
	 * perform an update
	 * @return
	 */
	public void update() {
		try {
			transferEvent("initialising transfer...");
			
		    Socket socket = new Socket(location,port);
	
			FileRepository fileRepos = new FileRepository(directories,excludeFiles);
	
			ObjectOutputStream objOut = new ObjectOutputStream(socket.getOutputStream());
	
			//send the file repository to the server
			objOut.writeObject(fileRepos);
			objOut.flush();
	
			//then get the response back from the server
			reciever.recieveFiles(socket.getInputStream(),tempDirectory);
			
		    socket.close();
		    objOut.close();
		    
			} catch (UnknownHostException e) {
				setErrorStatus("Unable to locate server");
			    e.printStackTrace();
			}
			catch (Exception e) {
				setErrorStatus(e.getMessage());
			    e.printStackTrace();
			}
	}
	
	private void setErrorStatus(String description) {
	    completed("error","error: "+description);	
	}
}
